TypeError: undefined is not an object (evaluating 'nod.images.fluid') -- - mapping

I am new in Gatsby and trying to place a image carousel on a modal. I have an array of images on Contentful and cannot map the images. I can do it outside of the modal but inside I can't reach to the array .
I tried to use different modals, different carousels but this is the best result that I can find.
These are the codes for carousel:
const CarouselUI = ({ position, handleClick, children }) => (
<Container>
{children}
<Arrow onClick={handleClick} data-position={position - 1}>{'<'}</Arrow>
<Arrow right onClick={handleClick} data-position={position + 1}>{'>'}</Arrow>
</Container>
);
const Carousel = makeCarousel(CarouselUI)
These are the codes for component
const Product = ({ node }) => {
const [showModal, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div>
<figure>
<Link href="/">
<Image fluid={node.image.fluid} alt={node.title} />
</Link>
<figcaption onClick={handleShow}>
<h4>Quick View</h4>
</figcaption>
<p>{node.title}</p>
<p>{node.price}</p>
<Modal className={product1Style.Modal} show={showModal} onHide={handleClose}>
<section className={product1Style.modalheader}>
<h1>{node.title}</h1>
</section>
<section className={product1Style.modalcontent}>
<Modal.Body className={product1Style.row + ' ' + product1Style.center} closeButton>
<div className={product1Style.col + ' ' + product1Style.colspan3}>
<h4>{node.price}</h4>
<div>
<button>Purchase</button>
</div>
</div>
<div className={product1Style.col + ' ' + product1Style.colspan4}>
<Carousel>
{node.images.map((nod) => {
return (
<Slide>
<Image fluid={nod.images.fluid} />
</Slide>
)
})}
</Carousel>
</div>
</Modal.Body>
</section>
</Modal>
</figure>
</div>
)
}
And this is my class:
class Product1 extends React.Component {
render() {
const ProductNecklace = this.props.data.productNecklace.edges
return (
<Layout>
<section className={product1Style.lowerBody}>
<section className={product1Style.product1Necklace}>
<h3>Product1 Necklace</h3>
<div className={product1Style.productnecklaceimage}>
{ProductNecklace.map(({ node }, i) => (
<Product node={node} key={node.id} />
))}
</div>
</section>
</section>
</Layout>
)
}
}

Your nod is the image itself, it's the iterable object from node.images, you can name it however you want. Without knowing the data structure, it's impossible to figure out how the map should be, but I guess that should look like:
<Carousel>
{node.images.map(image => {
console.log(image)
return (
<Slide>
<Image fluid={image.childImageSharp.fluid} />
</Slide>
)
})}
</Carousel>
If you provide how's the GraphQL query, I will be able to infer how are the object and the arrays. Assuming that your node.images holds an array of images, inside each image you will need to access first to childImageSharp property before reaching fluid. As I said, your GraphQL query will be easier to debug. The console.log above the return statement will help you to know the nested properties.

Related

Rendering new list item after adding it from a nested form. React hooks, redux, React Router V6

I am creating a list tracking app with React hooks, Redux, and Ruby on Rails. There is a List model, with a title as a string and completed as a boolean, and a ListItem model with descriptions as a string (the list item), completed boolean, and list_id as an integer.
I am using react route V6 for this and getting a little lost in re-rendering/ updating the page. Here is the breakdown of the application:
On the home screen, you can click to view all Lists and add a new list. when viewing all list each list title is displayed as a link to that list show page. The show page shows the list title, list items and a form to add another list item. Now where I am having trouble is being able to add a new list item, and it display on the page right after submission. Right now when I add a new item, and refresh the page it is not there. But if I click back to view all lists, then click that list again it shows up under the list items.
I tried using useNavigate to navigate to that list show page even though it is already on it but I am getting this error
Uncaught TypeError: Cannot destructure property 'list' of 'location.state' as it is null.
Here is all my components:
App.js
class App extends React.Component {
render(){
return (
<div className="App">
<Navbar/>
<br></br>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/lists" element={<Lists />} />
<Route path="/lists/new" element={<ListForm />} />
<Route path="/lists/:id" element={<ListContainer />} />
</Routes>
</div>
);
}
}
Lists.js
export default function Lists() {
const lists = useSelector(state => state.lists)
// replaces mapStateToProps
const dispatch = useDispatch()
// replaces mapDispatchToProps
useEffect(() => {
dispatch(fetchLists())
}, [])
return (
<div>
{Array.isArray(lists) && lists.map((list) => {
return (
<Link
key={list.id}
to={`/lists/${list.id}`}
state={{ list: list }}
>
<h2>{list.title}</h2>
</Link>
)
})}
</div>
)
}
ListContainer.js
export default function ListContainer() {
const location = useLocation();
const { list } = location.state;
console.log(list)
return (
<div>
<List list={list}/>
<ListItemForm list={list}/>
</div>
);
}
List.js
export default function List({list}) {
return (
<div>
<h4>{list.title}</h4>
{list.list_items.map((item) => {
return (
<div key={item.id}>
<li key={item.id}>{item.description}</li>
</div>
);
})}
<br></br>
</div>
);
}
and ListItemForm.js
export default function ListItemForm({list}) {
const [item, setItem] = useState("")
const dispatch = useDispatch()
const navigate = useNavigate()
function handleSubmit(e) {
e.preventDefault()
let newItem = {description: item, completed: false, list_id: list.id}
dispatch(createListItem(newItem, list.id))
setItem("")
navigate(`/lists/${list.id}`)
}
return (
<div>
<br></br>
<form onSubmit={handleSubmit}>
<label>Add to your list: </label>
<input value={item} onChange={(e) => setItem(e.target.value)} />
</form>
</div>
)
}
I have been stuck on this for quite some time now and not sure where to go from here or where I am going wrong. Any help is appreciated!!
Sometimes when you navigate to "/lists/:id" you send route state, sometimes you don't. It's undefined when you navigate to "/lists/:id" when adding new list items. This navigation to the route you are already on for editing a list is unnecessary.
Since you are using Redux I don't think there's any need to send a list item in route state at all. Use the id route parameter and your lists redux state to derive the specific list you want to view/edit.
Example
Given: <Route path="/lists/:id" element={<ListContainer />} />
Lists
function Lists() {
const dispatch = useDispatch();
const lists = useSelector((state) => state.lists);
useEffect(() => {
if (!lists.length) {
dispatch(fetchLists());
}
}, [dispatch, lists]);
return (
<div>
{lists.map((list) => (
<Link key={list.id} to={`/lists/${list.id}`}>
<h2>{list.title}</h2>
</Link>
))}
</div>
);
}
ListContainer
import { useParams } from 'react-router-dom';
function ListContainer() {
const { id } = useParams();
const lists = useSelector((state) => state.lists);
const list = lists.find((list) => list.id === id);
return (
<div>
<List list={list} />
<ListItemForm list={list} />
</div>
);
}
ListItemForm
function ListItemForm({ list }) {
const [item, setItem] = useState("");
const dispatch = useDispatch();
function handleSubmit(e) {
e.preventDefault();
dispatch(actions.createListItem(item, list.id));
setItem("");
}
return (
<div>
<br></br>
<form onSubmit={handleSubmit}>
<label>Add to your list: </label>
<input value={item} onChange={(e) => setItem(e.target.value)} />
</form>
</div>
);
}

Reset nested array in react-hook-form

I try to reset a nested array in react hook form without success
I created the following sandbox
sandbox
Your problem can be fixed by following the nested useFieldArray example here. That example is created by the library's author. I have no idea why it happens though, it may be a library bug or a quirk because the author never expects you to write code that way..
Basically you need to refactor your code by putting the nested fields in a child component instead of placing everything in one big component. So change this:
const { fields, remove } = useFieldArray({
control,
name: "names"
});
const { fields: nested } = useFieldArray({
control,
name: "names[0].nested"
});
<ul>
{fields.map((item, index) => {
return (
<li key={item.id}>
<input
name={`names[${index}].firstName`}
defaultValue={`${item.firstName}`}
ref={register()}
/>
<ul>
{nested.map((nestedItem, nestedIndex) => {
return (
<li key={item.id}>
<input
name={`names[${index}].nested[${nestedIndex}].lastName`}
defaultValue={`${nestedItem.lastName}`}
ref={register()}
/>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
To something like this:
Parent
const { fields, remove } = useFieldArray({
control,
name: "names"
});
<ul>
{fields.map((item, index) => {
return (
<li key={item.id}>
<input
name={`names[${index}].firstName`}
defaultValue={`${item.firstName}`}
ref={register()}
/>
<NestedArray
index={index}
control={control}
register={register}
/>
</li>
);
})}
</ul>
NestedArray
const { fields, remove } = useFieldArray({
control,
name: "names[0].nested"
});
return (
<ul>
{fields.map((nestedItem, nestedIndex) => {
return (
<li key={nestedItem.id}>
<input
name={`names[${index}].nested[${nestedIndex}].lastName`}
defaultValue={`${nestedItem.lastName}`}
ref={register()}
/>
</li>
);
})}
</ul>
);
Live Demo

How to register 'react-bootstrap-typeahead' component using React 'useForm' hook?

I'm building a simple html form using "react-hook-form" library: https://react-hook-form.com/
I've incorporated "react-bootstrap-typeahead" into the html form but haven't been able to register this component with 'useForm' hook. Hence, "react-bootstrap-typeahead" input data is ignored during onSubmit.
"react-bootstrap-typeahead" doesn't provide a "name" prop which makes it difficult to register the component.
I've read the 'useForm' documentation on the different options for registering this type of components but still don't understand how to achieve this: https://react-hook-form.com/get-started#Registerfields
Does anybody have faced such challenge before?
It would be great to see a working example to get a better idea on how to implement "react-bootstrap-typeahead" + "react-hook-form" in my application. Thanks!
Here's my sample code:
import useForm from 'react-hook-form';
import { Typeahead } from 'react-bootstrap-typeahead';
import 'react-bootstrap-typeahead/css/Typeahead.css';
const myForm = (props) => {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => {
// api post request with form data
})
};
const mydata = [ "one", "two", "three" ];
return (
<>
<form onSubmit={handleSubmit(onSubmit)} >
<div className="form-group">
{/* Here I'm registering text input using ref: */}
<input type="text" className="form-control" name="name" ref={register({ required: true })} />
</div>
<div className="form-group mb-0">
{/* How can I register the below component with useForm? */}
<Typeahead
id="multiple-typeahead"
clearButton
multiple
options={mydata}
/>
</div>
<button type="submit">Save</button>
</form>
</>
);
}
This is how i was able to register the component:
import useForm from 'react-hook-form';
import { useForm, Controller } from "react-hook-form";
import 'react-bootstrap-typeahead/css/Typeahead.css';
const myForm = (props) => {
const { register, handleSubmit, errors, control } = useForm();
const onSubmit = data => {
// api post request with form data
})
};
const mydata = [ "one", "two", "three" ];
return (
<>
<form onSubmit={handleSubmit(onSubmit)} >
<div className="form-group">
<input type="text" className="form-control" name="name" ref={register({ required: true })} />
</div>
<div className="form-group mb-0">
<Controller
as={Typeahead}
control={control}
name="typeahead_component"
rules={{ required: true }}
id="multiple-typeahead"
clearButton
multiple
options={mydata}
defaultValue=""
/>
</div>
<button type="submit">Save</button>
</form>
</>
);
}

Unable to access mutator functions in Wizard form page while using react-final-form

I am trying to create a Wizard form using react-final-form by referring to this code https://codesandbox.io/s/km2n35kq3v. For my use case I need some mutator functions to be used inside my form fields. This example illustrates how to do that - https://codesandbox.io/s/kx8qv67nk5?from-embed.
I am not sure how to access mutator functions in my form steps when I am using a wizard form instead of a single page form.
I tried to combine both the examples by modifying the <Form> component rendered by Wizard.js to pass in the mutators. However I cannot access these mutators in the Wizard form pages.
In Wizard.js
return (
<Form
mutators={{
// potentially other mutators could be merged here
...arrayMutators,
}}
render={({
handleSubmit,
submitting,
values,
pristine,
invalid,
form: {
mutators: {push, pop, remove},
},
}) => {
return (
<form onSubmit={handleSubmit}>
Another file index.js
<Wizard
initialValues={{ employed: true, stooge: "larry" }}
onSubmit={onSubmit}
>
<Wizard.Page>
<FieldArray name="customers">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component="input"
placeholder="First Name"
/>
<span
onClick={() => fields.remove(index)}
style={{ cursor: "pointer" }}
>
❌
</span>
</div>
))
}
</FieldArray>
</Wizard.Page>
</Wizard>
It errors out - remove is undefined in index.js
Look at this working example: https://codesandbox.io/s/znzlqvzvnx
changes I have made:
Wizard.js
static Page = ({ children, mutators }) => {
if(typeof children === 'function'){
return children(mutators);
}
return children;
};
...
<form onSubmit={handleSubmit}>
{
// activePage
<activePage.type {...activePage.props} mutators={mutators} />
}
...
index.js (only first <Wizard.page>)
<Wizard.Page>
{
({ upper }) => (
<React.Fragment>
<div>
<label>First Name</label>
<Field
name="firstName"
component="input"
...
</div>
</React.Fragment>
)
}
</Wizard.Page>

ReactJS not binding via map to state values

I'm working with an MVC5 project and running into an issue with React not binding an array. I had this working in an MVC Core project, but had to "regress" back to the old structure. Biggest change seemed to be in the controller, changing from JsonResult (Core MVC) to Json (MVC5) for the return type on the ajax call.
Here's the output from Chrome Developer Tools:
(removed due to lack of reputation points)
And, my code for my .jsx file:
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem">
<div className="lessLineHeight smallFont">
<div className='section group'>
<div className="col span_1_of_2" id={this.props.ordHeaderId}>
<text>{this.props.code}</text>
</div>
<div className='col span_1_of_2 text-right'>
<i className={this.props.apptIconString} aria-hidden='true'></i>
<i className={this.props.highValueIconString}></i>
<i className={this.props.hazmatIconString}></i>
</div>
</div>
<div className='section group'>
<div className='col span_6_of_10'>
<text title='Trading Partner - Client'>{this.props.tradingPartnerName}</text>
</div>
<div className='col span_4_of_10 text-right'>
<text className='overflowElip' title='Account Manager'>{this.props.accountManager}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_2'>
<text title={"Origin: " + this.props.originAddress + "; " + this.props.origContact}>{this.props.originAddress}</text>
</div>
<div className='col span_1_of_2 text-right'>
<text title={"Destination:" + this.props.destinationAddress + "; " + this.props.destContact}>{this.props.destinationCity}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_3'>${this.props.freightValue}</div>
<div className='col span_1_of_3 text-center'>
<a title='Promote Order to Load'>To Load</a>
</div>
<div className='col span_1_of_3 text-right' id={'datePlanned' + this.props.ordHeaderId}>
<text title='Pickup Date'>{this.props.dateCreated}</text>
</div>
</div>
</div>
</div>
);
}
});
var ItemList = React.createClass({
getInitialState: function () {
return { items: [] };
},
loadData: function () {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function (data) {
console.log(data);
this.setState({ items: data });
console.log(this.state.items);
$("#column1").find(".gridItem:odd").css({ "background-color": "#ddd" }).end().find(".gridItem:even").css({ "background-color": "#fff" });
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function () {
this.loadData();
/*window.setInterval(this.loadData, this.props.pollInterval);*/
},
render: function () {
if (this.state.items) {
console.log("State has items.");
var itemNodes = this.state.items.map(function (foo) {
return (
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
);
});
return (
<div className="itemList">
{itemNodes}
</div>
);
} else {
return null;
}
}
});
ReactDOM.render(
<ItemList url="/DispatchBoard/getColumn1Data" pollInterval={2000} />,
document.getElementById('column1')
);
As you can see from the image, the render: in the loadData function sees the items coming back from the ajax call, and then sets them to state, but when it comes time to map them, it does nothing.
Any ideas on what I'm not seeing?
EDIT
Here's a screen show showing the 'undefined' value(s) in one of the LineItems after failing to map properly. undefined values
EDIT #2
Here's a screenshot showing that the objects are hydrated and not being parsed. object present, not parsed
After seeing the screenshot you posted in EDIT #2
The issue is you're using different property name when accessing the data from foo while setting the properties on your component
So changing it from
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
to
<LineItem key={foo.ordHeaderId}
accountManager={foo.AccountManager}
...
should do the trick
That is use the exact property name from your foo object instead of using camel cased or some other version of it.
The if condition in <ItemList> render is wrong. It should be like
if(this.state.items.length > 0)
Everything else looks fine. But, you forgot to add the key to the <LineItem> component
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
... />
Here, you are passing key as a prop to the <LineItem> component but you forgot to set that key from the prop to the parent element.
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem" key={this.props.key}>
<div className="lessLineHeight smallFont">
....
)
}
})
This should remove the error/warning
From what I have experienced you can't pass key as a prop element. Remove this from you LineItem and see if it works. Let the warning persist. You can figure out a way to remove the warning later if this works.
<LineItem
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
Random User found the answer and it's contained in his comment.
The "key" to the problem was not capitalizing the properties that were to be mapped. Not sure why it worked the way it was in Core MVC, but, obviously, it doesn't work the same in MVC 4.

Resources