React state variable is undefined inside render method - javascript

I have a state variable dataSource that has some data in it.
In a parent component I have the following:
updateFeed = newItem => {
this.setState({ dataSource: this.state.dataSource.data.unshift(newItem) })
console.log(this.state.dataSource.data)
}
render() {
console.log(this.state.dataSource.data)
return (
<React.Fragment>
<Header />
<Route
path="/"
exact
component={props => (
<Feed {...props} feedData={this.state.dataSource.data} updateFeed={this.updateFeed} />
)}
/>
<Route path="/profile/:id" exact component={props => <Profile {...props} />} />
</React.Fragment>
)
}
updateFeed is called from the child component.
onSubmit = () => {
const newPost = newData // some new data
this.props.updateFeed(newPost)
}
The updateFeed function is getting executed on submit, and the console.log is giving the updated data. But inside the render function this.state.dataSource.data is undefined. What am I missing here?

You do dataSource: dataSource.data in your setState call, therefore dataSource.data in your render method will actually access dataSource.data.data which is probably undefined. May change updatedFeed to:
updateFeed = newItem => {
this.setState(prev => ({
dataSource: {
...prev.dataSource,
data: prev.dataSource.data.concat(newItem)
}
}));
}
Which ensures a pure state.

It is because previously, this.state.dataSource is an object having key data. So even you are setting new value in updateFeed but in the very next line, state has not been updated yet. React does this asynchronously. so your log statement is showing old data.
You need to update state like this
const dataSource = this.state.dataSource;
dataSource.data.unshift(newItem);
this.setState({ dataSource: dataSource })

Related

React: Cannot assign to read only property '_status' of object '#<Object>'

I've been struggling over this error for a while now. It happens when I try to open react-bootstrap Modal with dynamically passed lazy component referrence and props to render it inside. It worked with classic import.
First row points to some react's internal lazy handler:
This is how modals are handled inside my ModalProvider:
const ModalList = React.memo(({ modalList, closeModalByIndex, confirmModalExitByIndex }) =>
modalList.map((modalDef, index) => {
const closeModal = () => closeModalByIndex(index);
const onConfirmExitChange = (confirmExit) => confirmModalExitByIndex(index, confirmExit);
const props = { ...modalDef, key: index, closeModal, onConfirmExitChange };
switch (modalDef.type) {
case TYPE_LIST:
return (
<React.Suspense fallback={fallback}>
<ListModal {...props} />
</React.Suspense>
);
case TYPE_FORM:
return (
<React.Suspense fallback={fallback}>
<FormModal {...props} />
</React.Suspense>
);
case TYPE_LIST_MULTI:
return (
<React.Suspense fallback={fallback}>
<ListMultiModal {...props} />
</React.Suspense>
);
default:
return null;
}
})
);
And this is how it is passed:
const openListModal = (Component, componentProps) => openModal(Component, componentProps, TYPE_LIST);
Anyone with deeper understanding what could possibly cause this?
Found out by trial by error. It was caused by immer's produce function which builds read-only deep copy of object.
setModalList(
produce(modalList, (modalList) => {
modalList.push({ Component, componentProps, type, show: true });
})
);

useStates seem as undefined on props

I am trying to get some datas from child to parent. There is a way I usually do, and it totally works. But for one page I used:
<Link to={{
pathname: `/productBuy`,
state: { product, iconClick }
}}>
and when I send another prop from App.js to productBuy page, it's shown under product and it's undefined.
Codes from App.js :
const [productInformation, setProductInformation] = useState([]);
<Route path="/productBuy" render={props => <ProductBuy {...props} productInformation {productInformation} setProductInformation={setProductInformation} />} />
productBuy.js :
const ProductBuy = (productInfo, {productInformation,setProductInformation}) => {
return (
<div className="productBuy">
<div className="productBuyContainer">
<ProductLeft productInfo={productInfo.location.state} />
<ProductRight productInfo={productInfo.location.state} productInformation={productInformation} setProductInformation={setProductInformation}/>
</div>
</div>
);
}
When I console.log, my props are shown under product object as undefined. and when I invoke a function, an error appears: ProductRight.js:51 Uncaught TypeError: setProductInformation is not a function
Is there a way to solve this problem?
First of all you're missing the = after productInformation in the render prop:
<ProductBuy {...props} productInformation={productInformation} setProductInformation={setProductInformation} />
And the second issue is that you're unpacking the props incorrectly. Both productInformation and setProductInformation are available in the props argument (the first positional argument) in your function, but you're unpacking it in the second argument instead:
// INCORRECT
const ProductBuy = (productInfo, {productInformation,setProductInformation}) => { ... }
You can unpack it from the productInfo argument, which is an object that holds all the props:
const ProductBuy = (productInfo) => {
const { productInformation, setProductInformation } = productInfo;
return (
<div className="productBuy">
<div className="productBuyContainer">
<ProductLeft productInfo={productInfo.location.state} />
<ProductRight productInfo={productInfo.location.state} productInformation={productInformation} setProductInformation={setProductInformation}/>
</div>
</div>
);
}
You can also choose to unpack it at the top level:
const ProductBuy = ({ location, productInformation, setProductInformation }) => {
return (
<div className="productBuy">
<div className="productBuyContainer">
<ProductLeft productInfo={location.state} />
<ProductRight productInfo={location.state} productInformation={productInformation} setProductInformation={setProductInformation}/>
</div>
</div>
);
}
Add equal sign when passing the productInformation to props seems you forgot that in App.js
<Route path="/productBuy" render={props => <ProductBuy {...props} productInformation={productInformation} setProductInformation={setProductInformation} />} />

TypeError: Cannot read properties of undefined (reading 'name') - React , redux-toolkit

I fetch data from API by using react-redux Toolkit. Want to display default city weather datas when page render but it gives an error
TypeError: Cannot read properties of undefined (reading 'name')
Output - > Empty Array from index.js than API output from
WeatherSlice.js
export const fetchDefault = createAsyncThunk('weather/getWeather', async (selectedCity) => {
const res = await axios(`http://api.weatherapi.com/v1/forecast.json?key=ebb6c0feefc646f6aa6124922211211&q=${selectedCity}&days=10&aqi=no&alerts=no
`)
return res.data
});
<Typography className="label" variant="h5" sx={{pb:5}} component="div">
{getCity.location.name} // GivesTypeError
</Typography>
Home component
const getCity = useSelector((state) => state.weather.item);
useEffect(() => {
dispatch(fetchDefault(selectedCity))
console.log()
}, [dispatch])
App.js
<Switch>
<Route path="/about" component={About} >
<About />
</Route>
<Route path="/" component={Home}>
<Home />
</Route>
</Switch>
Store.js
export const store = configureStore({
reducer: {
weather : weatherSlice.reducer,
},
})
WeatherSlice.js
export const weatherSlice = createSlice({
name: "weather",
initialState : {
item : [],
},
reducers:{},
extraReducers:{
[fetchDefault.fulfilled]: (state , action) => {
state.item = action.payload;
console.log(state.item)
},
[fetchDefault.pending]: (state , action) => {
console.log("sadsad")
}
}
I checked the api you used (https://api.weatherapi.com/v1/forecast.json?key=ebb6c0feefc646f6aa6124922211211&q=Istanbul&days=10&aqi=no&alerts=no) to see what it returns.
Response data is an object with fields: location, current and forecast.
So at first, I think the initial state for "item" should not be an empty array because api does not returns an array instead it should be undefined or empty object.
Then, the main reason that the TypeError exists is you can not retrieve data or can not fill the item in the state. You should check the data that returns from the api. Can you successfully retrieve the data and fill the state with it?
Reason of TypeError: If you have an empty array or empty object and try to access a field through this element (like item.location) it will not show an error, but if you try to access a field of a field (like item.location.name) the TypeError occurs.
Also checking the object before component will be safer. Like:
// can be undefined comparison or getCity.length > 0 etc
{getCity !== undefined && (
<Typography className="label" variant="h5" sx={{pb:5}} component="div">
{getCity.location.name} // GivesTypeError
</Typography>
)}

React passing down hooks causes rerender and loss of focus on input

I've got a parent component in which I initialize some piece of state, which I then pass down to the children components so that they can update that. However, when the update is triggered, the component tree is re-rendered and my inputs lose focus. Adding a key did not help.
// App.tsx
export function App(props) {
const useVal = useState("");
return (
<Router>
<Switch>
<Route
exact
path="/"
component={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
// ...
</Router>
);
}
// StartScreen.tsx
interface StartScreenProps {
useVal: [string, React.Dispatch<React.SetStateAction<string>>];
}
function bindState<T>(
[value, setState]: [T, React.Dispatch<React.SetStateAction<T>>]
) {
return {
value,
onChange: ({ value }: { value: T }) => setState(value)
}
}
export const StartScreen = (props: StartScreenProps) => {
return (
<form>
<InputField
key="myInput"
{...bindState(props.useVal)}
/>
</form>
);
}
So, now when I start typing on my InputField (which is basically a wrapper on an <input>) on StartScreen.tsx, the input constantly loses focus as the component is totally re-rendered (I can see it in the DOM).
This happens because you are passing a function to the Route's component prop (I assume you are using react-router-dom) :
From the docs :
If you provide an inline function to the component prop, you would
create a new component every render. This results in the existing
component unmounting and the new component mounting instead of just
updating the existing component.
To solve this problem use the render prop :
<Route
exact
path="/"
render={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.

Component only rending if I start the flow from the homepage

I am having an issue with my application. My user component only loads UserCard when I start the application from the homepage then click users link there... if I just refresh the users URL... UserCard doesn't get loaded which means something is wrong with my this.props.users. I do see that in chrome it says: Value below was evaluated just now when I refresh but when I go through the flow it doesn't say that. Any help will be appreciated.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentDidMount() {
users = []
axios.get('/getall').then((res) => {
for(var d in res.data) {
users.push(new User(res.data[d]));
}
});
this.setState({ users });
}
render() {
const { users } = this.state;
return (
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path='/users' render={(props) => <Users {...props} users={users} />}/>
</Switch>
</Router>
)
}
}
PrivateRoute:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<Component {...props} /> )} />
)
User.js
export default class Users extends Component {
render() {
console.log(this.props.users);
return (
<Row>
{this.props.users.map(u =>
<UserCard key={u.name} user={u}/>
)}
</Row>
);
}
}
export class User {
constructor(obj) {
for (var prop in obj){
this[prop] = obj[prop];
}
}
getURLName() {
return this.name.replace(/\s+/g, '-').toLowerCase();
}
}
class UserCard extends Component {
render() {
return (
<Link to={'/users/' + this.props.user.getURLName()} >
<div>
// Stuff Here
</div>
</Link>
);
}
}
As per the comments:
The issue here is how you're setting state. You should never modify state directly since this will not cause the component to rerender See the react docs
Some additional thoughts unrelated to the question:
As per the comments - use function components whenever possible, especially with hooks on the way
There is probably no need to create a User class, only to new up little user objects. Simply use plain old JS objects and calculate the link url right in the place its used:
render() {
const { user } = this.props
return <Link to={`/users/${user.name.replace(/\s+/g, '-').toLowerCase()}`} />
}
It might be a good idea to start using a linter such as eslint. I see that you're declaring users = [] without using let or const (don't use var). This is bad practice since creating variables in this way pollutes the global name space. Linters like eslint will help you catch issues like this while you're coding.

Categories