React Component rewrites parent's state - javascript

i'm pretty new at React so i need some help
i have an component which has structure smth like that
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
...
portfolio: []
...
}
...
this.userDataUpdatesSubscribe = this.userDataUpdatesSubscribe.bind(this);
this.dealComplete = this.dealComplete.bind(this);
...
}
async componentDidMount() {
...
await this.userDataUpdatesSubscribe();
...
}
userDataUpdatesSubscribe(){
//fs here is a Firebase Firestore SDK
//firebase.initializeApp(firebaseConfig);
//const fs = firebase.firestore();
fs.collection('users').doc(...).onSnapshot(
e => {
this.portfolioInit(e.data().portfolio).then(v =>
this.setState({
portfolio: v,
...
})
);
}
);
}
async portfolioInit(stocks){
let a = [];
for (const v of stocks) {
let i;
await this.getPrice(v.ticker).then(e => {
i = e.trade.p
});
a.push({
ticker: v.ticker,
name: StocksData[v.ticker].name,
price: await i,
avgPrice: v.avgPrice,
count: v.count
});
}
return a;
}
async getPrice(ticker, date=null){
let a;
let u = ...;
await fetch(
u,
{
...
}
).catch(() => {...}).then(
async e => {
a = await e.json();
}
)
return await Promise.resolve(a);
}
render(){
<...>
<TheProblemComponent p={this.state.portfolio} .../>
</...>
}
}
so i have a component where state updates on firestore snapshot. i don't store prices in database but i need it so i use getPrice method which returns me price. when i've got all prices the state updates. then i convey data to which has structure like
const TheProblemComponent = (p) => {
const makeDeal() => {
let x = p.p;
...
some calculations
...
for(let i = 0; i < x.lenght; i++){
x[i] = {
ticker: x[i].ticker,
avgPrice: x[i].avgPrice,
count: x[i].count
} // so here i just delete price and name properties which are from props
}
fs.collection('users').doc(...).update({portfolio: x, ...}).then(() => {
...some actions
})
}
return <Button
onClick={() => {console.log(p.p); makeDeal()}}
></Button>
}
so again. i have a parent component which state updates on firestore snapshot. portfolio in database has avgPrice, count and ticker. in my component it also has price which i receive from the getPrice method and name which is constant. this state portfolio i send to ProblemComponent as props which should not modify the parent's state but it does. console.log() prints array without price and name even when executes before makeDeal function. i've tried to store price and name in DB but i want not to do it

It appears you are mutating the parent state via the passed prop because you are mutating the p prop.
const makeDeal() => {
let x = p.p; // <-- saved reference to p.p (portfolio??)
...
some calculations
...
for(let i = 0; i < x.lenght; i++){
x[i] = { // <-- mutation!!
ticker: x[i].ticker,
avgPrice: x[i].avgPrice,
count: x[i].count
}
}
fs.collection('users')
.doc(...)
.update({ portfolio: x, ... })
.then(() => {
...some actions
});
}
To resolve, shallow copy the data you want to update. I suggest also using more descriptive variable names so the code is more readable.
const makeDeal() => {
const portfolio = [...p.p]; // <-- copy array into new array reference
...
some calculations
...
for(let i = 0; i < portfolio.length; i++){
portfolio[i] = { // <-- update the copy
ticker: portfolio[i].ticker,
avgPrice: portfolio[i].avgPrice,
count: portfolio[i].count
}
}
fs.collection('users')
.doc(...)
.update({ portfolio, ... })
.then(() => {
...some actions
});
}

Related

Zustand setState outside of component doesn't update

My store is set inside store.js as all the other Zustand stores:
const retryStore = create(set => ({
retry_n: 0,
setGRetry: (retry_n) => set(state => ({
...state,
retry_n,
})),
}));
export { retryStore };
inside my #/utils/get.js file I try to update the number by 1, using setState as:
const retry_n = retryStore.getState().retry_n
const xxx = xxxStore.getState().xxx
const xxxx = xxxxStore.getState().xxxx
var j=0;
const maxretry = 20;
const myFetch = async () => {
try {
let url = `...`
while(j < maxretry){
console.log('while try n:',j) // 1,2,3... ecc
console.log('get.js retry_n:',retry_n) // always 0
if (isItRetry){ // TRUE
retryStore.setState({ retry_n: retry_n + 1})
console.log('playThis retry_n now:',retry_n) // always 0
}else{
...
}
if (a != b){ ... return }
j++
}
etc...
shouldn't this increment by 1 each loop? why is always 0? I use other stores the same way in the same file and all work but this one.

When routing mswjs/data populates the database with new items and removes the previous one, making it inaccessible

I use next-redux-wrapper, MSW, #mswjs/data and redux-toolkit for storing my data in a store as well as mocking API calls and fetching from a mock Database.
I have the following scenario happening to me.
I am on page /content/editor and in the console and terminal, I can see the data was fetched from the mock database and hydrated from getStaticProps of Editor.js. So now IDs 1 to 6 are inside the store accessible.
Now I click on the PLUS icon to create a new project. I fill out the dialog and press "SAVE". a POST request starts, it's pending and then it gets fulfilled. The new project is now in the mock DB as well as in the store, I can see IDs 1 to 7 now.
Since I clicked "SAVE" and the POST request was successful, I am being routed to /content/editor/7 to view the newly created project.
Now I am on Page [id].js, which also fetched data from the mock DB and then it gets stored and hydrated into the redux store. The idea is, it takes the previous store's state and spreads it into the store, with the new data (if there are any).
Now the ID 7 no longer exists. And IDs 1 to 6 also don't exist anymore, instead, I can see in the console and terminal that IDs 8 to 13 were created, and the previous ones are no more.
Obviously, this is not great. When I create a new project and then switch the route, I should be able to access the newly created project as well as the previously created ones. But instead, they all get overwritten.
It either has something to do with the next-redux-wrapper or MSW, but I am not sure how to make it work. I need help with it. I will post some code now:
Code
getStaticProps
// path example: /content/editor
// Editor.js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale }) => {
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
},
};
}
);
// path example: /content/editor/2
// [id].js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale, params }) => {
const { id } = params;
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
// store.dispatch(fetchProjectById(id)), // issue: fetching by ID returns null
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
id,
},
};
}
);
Mock Database
Factory
I am going to shorten the code to the relevant bits. I will remove properties for a project, as well es helper functions to generate data.
const asscendingId = (() => {
let id = 1;
return () => id++;
})();
const isDevelopment =
process.env.NODE_ENV === 'development' || process.env.STORYBOOK || false;
export const projectFactory = () => {
return {
id: primaryKey(isDevelopment ? asscendingId : nanoid),
name: String,
// ... other properties
}
};
export const createProject = (data) => {
return {
name: data.name,
createdAt: getUnixTime(new Date()),
...data,
};
};
/**
* Create initial set of tasks
*/
export function generateMockProjects(amount) {
const projects = [];
for (let i = amount; i >= 0; i--) {
const project = createProject({
name: faker.lorem.sentence(faker.datatype.number({ min: 1, max: 5 })),
dueDate: date(),
fontFamily: getRandomFontFamily(),
pageMargins: getRandomPageMargins(),
textAlign: getRandomTextAlign(),
pageNumberPosition: getRandomPageNumberPosition(),
...createWordsCounter(),
});
projects.push(project);
}
return projects;
}
API Handler
I will shorten this one to GET and POST requests only.
import { db } from '../../db';
export const projectsHandlers = (delay = 0) => {
return [
rest.get('https://my.backend/mock/projects', getAllProjects(delay)),
rest.get('https://my.backend/mock/projects/:id', getProjectById(delay)),
rest.get('https://my.backend/mock/projectsNames', getProjectsNames(delay)),
rest.get(
'https://my.backend/mock/projects/name/:id',
getProjectsNamesById(delay)
),
rest.post('https://my.backend/mock/projects', postProject(delay)),
rest.patch(
'https://my.backend/mock/projects/:id',
updateProjectById(delay)
),
];
};
function getAllProjects(delay) {
return (request, response, context) => {
const projects = db.project.getAll();
return response(context.delay(delay), context.json(projects));
};
}
function postProject(delay) {
return (request, response, context) => {
const { body } = request;
if (body.content === 'error') {
return response(
context.delay(delay),
context.status(500),
context.json('Server error saving this project')
);
}
const now = getUnixTime(new Date());
const project = db.project.create({
...body,
createdAt: now,
maxWords: 10_000,
minWords: 7000,
targetWords: 8500,
potentialWords: 1500,
currentWords: 0,
});
return response(context.delay(delay), context.json(project));
};
}
// all handlers
import { buildingBlocksHandlers } from './api/buildingblocks';
import { checklistHandlers } from './api/checklist';
import { paragraphsHandlers } from './api/paragraphs';
import { projectsHandlers } from './api/projects';
import { tasksHandlers } from './api/tasks';
const ARTIFICIAL_DELAY_MS = 2000;
export const handlers = [
...tasksHandlers(ARTIFICIAL_DELAY_MS),
...checklistHandlers(ARTIFICIAL_DELAY_MS),
...projectsHandlers(ARTIFICIAL_DELAY_MS),
...buildingBlocksHandlers(ARTIFICIAL_DELAY_MS),
...paragraphsHandlers(ARTIFICIAL_DELAY_MS),
];
// database
import { factory } from '#mswjs/data';
import {
buildingBlockFactory,
generateMockBuildingBlocks,
} from './factory/buildingblocks.factory';
import {
checklistFactory,
generateMockChecklist,
} from './factory/checklist.factory';
import { paragraphFactory } from './factory/paragraph.factory';
import {
projectFactory,
generateMockProjects,
} from './factory/project.factory';
import { taskFactory, generateMockTasks } from './factory/task.factory';
export const db = factory({
task: taskFactory(),
checklist: checklistFactory(),
project: projectFactory(),
buildingBlock: buildingBlockFactory(),
paragraph: paragraphFactory(),
});
generateMockProjects(5).map((project) => db.project.create(project));
const projectIds = db.project.getAll().map((project) => project.id);
generateMockTasks(20, projectIds).map((task) => db.task.create(task));
generateMockBuildingBlocks(10, projectIds).map((block) =>
db.buildingBlock.create(block)
);
const taskIds = db.task.getAll().map((task) => task.id);
generateMockChecklist(20, taskIds).map((item) => db.checklist.create(item));
Project Slice
I will shorten this one as well to the relevant snippets.
// projects.slice.js
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
current,
} from '#reduxjs/toolkit';
import { client } from 'mocks/client';
import { HYDRATE } from 'next-redux-wrapper';
const projectsAdapter = createEntityAdapter();
const initialState = projectsAdapter.getInitialState({
status: 'idle',
filter: { type: null, value: null },
statuses: {},
});
export const fetchProjects = createAsyncThunk(
'projects/fetchProjects',
async () => {
const response = await client.get('https://my.backend/mock/projects');
return response.data;
}
);
export const saveNewProject = createAsyncThunk(
'projects/saveNewProject',
async (data) => {
const response = await client.post('https://my.backend/mock/projects', {
...data,
});
return response.data;
}
);
export const projectSlice = createSlice({
name: 'projects',
initialState,
reducers: {
// irrelevant reducers....
},
extraReducers: (builder) => {
builder
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
.addCase(fetchProjects.pending, (state, action) => {
state.status = 'loading';
})
.addCase(fetchProjects.fulfilled, (state, action) => {
projectsAdapter.addMany(state, action.payload);
state.status = 'idle';
action.payload.forEach((item) => {
state.statuses[item.id] = 'idle';
});
})
.addCase(saveNewProject.pending, (state, action) => {
console.log('SAVE NEW PROJECT PENDING', action);
})
.addCase(saveNewProject.fulfilled, (state, action) => {
projectsAdapter.addOne(state, action.payload);
console.group('SAVE NEW PROJECT FULFILLED');
console.log(current(state));
console.log(action);
console.groupEnd();
state.statuses[action.payload.id] = 'idle';
})
// other irrelevant reducers...
},
});
This should be all the relevant code. If you have questions, please ask them and I will try to answer them.
I have changed how the state gets hydrated, so I turned this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
Into this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.group('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
state.statuses = { ...state.statuses, ...statuses };
projectsAdapter.upsertMany(state, action.payload.projects.entities);
})
I used the adapter to upsert all entries.

How to set state of cart when no items?

In my E-Commerce project, the products are stored in localStorage. componentDidMount() gets all these products from localStorage and displays it. How to state the state or condition when no products are available.
componentDidMount = () => {
this.setProducts();
// Gets all products in cart from localStorage
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
// How to set the condition when no products in Cart?
}
// Set all the products on Main Page
setProducts = () => {
let tempProducts = [];
storeProducts.forEach(item => {
const singleItem = {...item};
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return {products: tempProducts};
});
};
// Here products are added to cart and stored in localStorage
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return { products: tempProducts, cart: [...this.state.cart,
product] };
},
() => {
this.addTotal();
localStorage.setItem('myCart', JSON.stringify(this.state.cart))
});
}
I have also tried to make following changes, but, no effect. In componentDidMount()
componentDidMount() {
if(this.state.cart.length > 0) {
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
} else {
this.setState(() => {
return {cart:[]}
})
}
}
// Clear Cart
clearCart = () => {
this.setState(() => {
return {cart:[]}
}, () => {
this.setProducts();
this.addTotal();
})
localStorage.removeItem('myCart')
}
When I remove code of setState (shown in the beginning) from componentDidMount() displays empty cart message, which is expected else, if the cart is cleared and refreshed browser throws 'cart.length' error. Any possible solution?
JSON.parse will return an object. It depends on your data structure but there is no cart.lendth for the object. So that is your first problem. So for the below example, I store the parsed value as an array.
Also, if state.cart is not initiated, there is no .length property for it.
For your second problem have a look at the below version of your componentDidMount:
componentDidMount() {
if(Array.isArray(this.state.cart) && this.state.cart.length) {
const cart = localStorage.getItem('myCart') || [];
this.setState({cart: [JSON.parse(cart)]}), this.addTotal);
} else {
this.setState({ cart:[] });
}
}
Again, it depends on your implementation, but you might need to initiate the component's state with cart: localStorage.getItem('myCart') || [] or doing what I have done above. I'm basically checking if cart is an array && it has length then parse it otherwise initiate the array.
Finally I got the solution as below
const cart = localStorage.getItem('myCart')
this.setState({cart: JSON.parse(cart) ? JSON.parse(cart) : []}, this.addTotal)
Just modified the code and works perfectly without any issues

React Native Flatlist Not Rerendering Redux

My FlatList does not update when the props I pass from redux change. Every time I send a message I increase everyones unread message count in both firebase and in my redux store. I made sure to include key extractor and extra data, but neither helps. The only thing that changes the unread message count is a reload of the device. How do I make sure the flatList updates with MapStateToProps. I made sure to create a new object by using Object.Assign:
action:
export const sendMessage = (
message,
currentChannel,
channelType,
messageType
) => {
return dispatch => {
dispatch(chatMessageLoading());
const currentUserID = firebaseService.auth().currentUser.uid;
let createdAt = firebase.database.ServerValue.TIMESTAMP;
let chatMessage = {
text: message,
createdAt: createdAt,
userId: currentUserID,
messageType: messageType
};
FIREBASE_REF_MESSAGES.child(channelType)
.child(currentChannel)
.push(chatMessage, error => {
if (error) {
dispatch(chatMessageError(error.message));
} else {
dispatch(chatMessageSuccess());
}
});
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child(channelType)
.child(currentChannel).child('users')
UNREAD_MESSAGES.once("value")
.then(snapshot => {
snapshot.forEach(user => {
let userKey = user.key;
// update unread messages count
if (userKey !== currentUserID) {
UNREAD_MESSAGES.child(userKey).transaction(function (unreadMessages) {
if (unreadMessages === null) {
dispatch(unreadMessageCount(currentChannel, 1))
return 1;
} else {
alert(unreadMessages)
dispatch(unreadMessageCount(currentChannel, unreadMessages + 1))
return unreadMessages + 1;
}
});
} else {
UNREAD_MESSAGES.child(userKey).transaction(function () {
dispatch(unreadMessageCount(currentChannel, 0))
return 0;
});
}
}
)
})
};
};
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
let currentUserID = firebaseService.auth().currentUser.uid;
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// flag whether you can check in or not
if (currentMountain) {
dispatch(checkInAvailable());
} else {
dispatch(checkInNotAvailable());
}
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.on("value", snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",
snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function () {
dispatch(loadPublicChannelsSuccess(data));
}, 150);
});
};
};
Reducer:
case types.UNREAD_MESSAGE_SUCCESS:
const um = Object.assign(state.unreadMessages, {[action.info]: action.unreadMessages});
return {
...state,
unreadMessages: um
};
Container- inside I hook up map state to props with the unread messages and pass to my component as props:
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
unreadMessages: state.chat.unreadMessages,
};
}
Component:
render() {
// rendering all public channels
const renderPublicChannels = ({ item, unreadMessages }) => {
return (
<ListItem
title={item.info.Name}
titleStyle={styles.title}
rightTitle={(this.props.unreadMessages || {} )[item.id] > 0 && `${(this.props.unreadMessages || {} )[item.id]}`}
rightTitleStyle={styles.rightTitle}
rightSubtitleStyle={styles.rightSubtitle}
rightSubtitle={(this.props.unreadMessages || {} )[item.id] > 0 && "unread"}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
return (
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
keyExtractor={(item, index) => index.toString()}
extraData={[this.props.publicChannels, this.props.unreadMessages]}
removeClippedSubviews={false}
/>
</View>
);
}
}
Object.assign will merge everything into the first object provided as an argument, and return the same object. In redux, you need to create a new object reference, otherwise change is not guaranteed to be be picked up. Use this
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
// or
const um = {...state.unreadMessages, [action.info]: action.unreadMessages }
Object.assign() does not return a new object. Due to which in the reducer unreadMessages is pointing to the same object and the component is not getting rerendered.
Use this in your reducer
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});

cannot read property of undefined when initialize state in react

I'm trying to make a forecast app with React and Flux. I fetch the data from Yahoo Weather API, and put the data to my store with a callback in jsonp request.Then in the View, I get the data (in componentDidMount())from store as a state and pass some properties of it to child components.
The data(this.state.store), which is a Object, has two properties, called condition and forecast.The problem is that if I want to pass the this.state.store.condition(or forecast) to the child, it says TypeError: Cannot read property 'condition' of undefined. But if I just try to access this.state.store(for example, console.log(this.state.store)), there is no error.
Also, if I try to access this.state.store.condition in a try-catch statement, and log the error when there is one, I do access the condition successfully with the console printed TypeError above mentioned.
Here is my codes:
store:
const CHANGE_EVENT = 'change';
let _app = {};
// create a city
function create(city, data) {
_app[city.toUpperCase()] = {
condition: data.condition,
forecast: data.forecast,
};
}
const AppStore = Object.assign({}, EventEmitter.prototype, {
getAll() {
return _app;
},
emitChange() {
this.emit(CHANGE_EVENT);
},
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
// register callback
AppDispatcher.register((action) => {
switch (action.actionType) {
case AppConstants.CREATE_CITY: {
create(action.city, action.data);
AppStore.emitChange();
break;
}
// other cases
default:
// noop
}
});
actions:
function callback(city, data) {
console.log(data);
const action = {
actionType: AppConstants.CREATE_CITY,
city,
data,
};
AppDispatcher.dispatch(action);
}
const AppActions = {
create(city) {
getDataFromAPI(city, callback);
},
};
utils:
function getDataFromAPI(query, callback) {
let data;
const url = `https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where u='c' AND woeid in (select woeid from geo.places(1) where text="${query}")&format=json`;
superagent
.get(url)
.use(jsonp)
.end((err, res) => {
console.log(res.body.query.results.channel.item);
data = res.body.query.results.channel.item;
callback(query, data);
});
}
views:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
store: Store.getAll(),
currentCity: 'BEIJING',
};
this.onChange = this.onChange.bind(this);
this.getCurrentCity = this.getCurrentCity.bind(this);
}
componentWillMount() {
AppActions.create('BEIJING');
}
componentDidMount() {
Store.addChangeListener(this.onChange);
}
onChange() {
this.setState({ store: Store.getAll() });
}
getCurrentCity(city) {
this.setState({ currentCity: city.toUpperCase() });
}
componentWillUnmout() {
Store.removeChangeListener(this.onChange);
}
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
// console.log(this.state.store[this.state.currentCity])<--UNDEFINED
// console.log(this.state.store[this.state.currentCity].condition);<--CANNOT READ PROPERTY
^
|
ERROR ON THIS 2 STATEMENTS
try {
condition = this.state.store[this.state.currentCity].condition;
forecast = this.state.store[this.state.currentCity].forecast;
text = condition.text.toUpperCase();
temp = condition.temp;
currentWeatherCode = condition.code;
currentWeatherClassName = setWeatherIcon(currentWeatherCode);
date = condition.date;
for (let i = 0; i < 6; i++) {
forecastWeatherCode.push(forecast[i].code);
forecastWeatherClassName.push(setWeatherIcon(forecastWeatherCode[i]));
forecastDate.push(forecast[i].date);
}
} catch (err) {
console.log(err);<--STILL ERROR, BUT I DO ACCESS THE PROP CONDITION IN THIS WAY
}
return (
<div>
<Today
city={this.state.currentCity}
weatherStatus={text}
tempreture={temp}
currentWeatherClassName={currentWeatherClassName}
date={date}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('#app'));
It seems to me that you are trying to access the this.state.store[this.state.currentCity] property before it is fetched from the remote API.
You could add some sort of indication that the data is still being fetched like this.
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
if (!this.state.store.hasOwnProperty(this.state.currentCity)) {
return <div>Loading...</div>;
}
... the rest of your original code
}
When it is done loading the setState() method is invoked and render() is called again. The second time it will fall trough the if and run your code.

Categories

Resources